package com.xxx.auth.common.interceptor;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.xxx.auth.common.util.DataUtil;
import com.xxx.auth.common.util.HmacUtil;
import com.xxx.auth.entity.Application;
import com.xxx.auth.repository.ApplicationRepository;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import lombok.AllArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.TreeMap;
import java.util.stream.Collectors;
import java.util.stream.Stream;

@Slf4j
@AllArgsConstructor
@Component
public class AuthenticationInterceptor implements HandlerInterceptor {

    private final ApplicationRepository applicationRepository;
    private final ObjectMapper objectMapper;

    @Override
    public boolean preHandle(HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) throws Exception {
        if (request.getRequestURI().equals("/authentication/v1/user/register") ||
                request.getRequestURI().equals("/authentication/v1/application/create")) {
            return true;
        }

        if (!(handler instanceof HandlerMethod)) {
            return true;
        }

        String userId = request.getHeader("User-Id");
        String appId = request.getHeader("Application-Id");
        String timestamp = request.getHeader("Timestamp");
        String sign = request.getHeader("Sign");

        if (StringUtils.isBlank(userId) || StringUtils.isBlank(appId) || StringUtils.isBlank(timestamp) || StringUtils.isBlank(sign)) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            String message = "Missing required headers: ['User-Id', 'Application-Id', 'Timestamp', 'Sign']";
            warn(message, request);
            setResponse(message, response);
            return false;
        }

        Application application = DataUtil.getData(applicationRepository, appId, Application::getStatus);
        if (Objects.isNull(application)) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            String message = "NO API";
            warn(message, request);
            setResponse(message, response);
            return false;
        }

        Map<String, String> allParams = getAllParameters(request);
        // 构建用于签名的字符串
        TreeMap<String, String> sortedParams = new TreeMap<>(allParams);
        sortedParams.put("User-Id", userId);
        sortedParams.put("Application-Id", appId);
        // 使用TreeMap对所有参数进行排序
        String paramString = sortedParams.entrySet().stream().map(entry -> entry.getKey() + "=" + entry.getValue()).collect(Collectors.joining("&"));

        String calculatedSign = HmacUtil.sha256(paramString, application.getAppSecret());
        log.info("calculatedSign: {}", calculatedSign);

        if (!calculatedSign.equalsIgnoreCase(sign)) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            String message = "SIGN ERROR";
            warn(message, request);
            setResponse(message, response);
            return false;
        }

        try {
            // 验证时间戳, 超过三分钟则失效
            long timestampLong = Long.parseLong(timestamp);
            if (timestampLong < System.currentTimeMillis() - (3 * 60 * 1000) || timestampLong > System.currentTimeMillis() + (3 * 60 * 1000)) {
                response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
                String message = "TIMEOUT";
                warn(message, request);
                setResponse(message, response);
                return false;
            }
        } catch (NumberFormatException e) {
            String message = "TIMESTAMP ERROR";
            warn(message, request);
            setResponse(message, response);
            return false;
        }

        boolean methodBoolean = application.getPermissionSet().stream().anyMatch(item -> pathMatchesPattern(item.getName(), getRequestPathPattern(request.getRequestURI())));
        if (!methodBoolean) {
            response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
            String message = "PERMISSION ERROR";
            warn(message, request);
            setResponse(message, response);
            return false;
        }

        info(request);
        return true;
    }

    /**
     * 获取所有参数，包括URL参数、表单参数和JSON体
     *
     * @param request 请求
     * @return 返回所有参数
     * @throws IOException 异常
     */
    private Map<String, String> getAllParameters(HttpServletRequest request) throws IOException {
        Map<String, String> allParams = new HashMap<>();

        request.getParameterMap().forEach((key, values) -> allParams.put(key, values[0]));

        if ("application/json".equals(request.getContentType())) {
            RequestWrapper requestWrapper = new RequestWrapper(request);
            String body = requestWrapper.getBody();

            if (!body.isEmpty()) {
                try {
                    Map<String, Object> jsonMap = objectMapper.readValue(body, new TypeReference<>() {
                    });
                    jsonMap.forEach((key, value) -> allParams.put(key, value != null ? value.toString() : ""));
                } catch (IOException e) {
                    log.error(e.getMessage());
                }
            }
        }

        return allParams;
    }

    private String getRequestPathPattern(String requestUri) {
        // 将路径参数部分替换为通配符 *
        return requestUri.replaceAll("\\{[^/]+}", "*");
    }

    private boolean pathMatchesPattern(String permissionPath, String requestPath) {
        String[] permissionParts = permissionPath.split("/");
        String[] requestParts = requestPath.split("/");

        if (permissionParts.length != requestParts.length) {
            return false;
        }

        for (int i = 0; i < permissionParts.length; i++) {
            if (!permissionParts[i].equals("*") && !permissionParts[i].equals(requestParts[i])) {
                return false;
            }
        }

        return true;
    }

    private void info(HttpServletRequest request) {
        log.info("Authentication passed: {}", getRequestInfo(request));
    }

    private void warn(String warnMessage, HttpServletRequest request) {
        log.warn("{}: {}", warnMessage, getRequestInfo(request));
    }

    private String getRequestInfo(HttpServletRequest request) {
        return String.format("%s,%s,%s,%d,%s", request.getRemoteHost(), request.getRequestURI(), request.getMethod(), request.getRemotePort(), getHeaderString(request));
    }

    private String getHeaderString(HttpServletRequest request) {
        return Stream.of("User-Id", "Application-Id", "Timestamp", "Sign").map(header -> header + "=" + request.getHeader(header)).collect(Collectors.joining("&"));
    }

    private void setResponse(String message, HttpServletResponse response) {
        response.setContentType("application/json");
        try {
            response.getWriter().println("{\"code\": \"400\", \"message\": \"" + message + "\"}");
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}